/*
 * FindReplacePanel
 * Copyright (C) 2011, Aleksey Nikiforov
 *
 * This file is part of FindReplacePanel.
 *
 * FindReplacePanel is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * FindReplacePanel is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package simplex3d.console.findreplace;

import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.HashMap;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.prefs.Preferences;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.ComboBoxEditor;
import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.KeyStroke;


/**
 *
 * @author Aleksey Nikiforov (lex)
 */
class SearchDialog {

    public static final String KEY_FIND_HISTORY = "FindReplacePanel.FindHistory";
    public static final String KEY_REPLACE_HISTORY = "FindReplacePanel.ReplaceHistory";
    
    private static final HashMap<String, SearchParams> lastUsedFind = new HashMap<String, SearchParams>();
    private static final HashMap<String, SearchParams> lastUsedReplace = new HashMap<String, SearchParams>();


    private final Action matchCaseAction;
    private final Action wholeWordsAction;
    private final Action regularExpressionAction;
    private final Action highlightAction;
    
    private final JComponent parent;
    private final PrefsComboBoxModel findModel;
    private final PrefsComboBoxModel replaceModel;

    private final FindComponent findPanel;
    private final ReplaceComponent replacePanel;
    private Component currentPanel = null;

    private boolean dialogEnabled = false;
    private int dialogType = FindReplacePanel.DIALOG_FIND;

    private Preferences preferences;


    public SearchDialog(
        JComponent parent,
        Map<String, Action> findReplaceActions, Map<String, Action> optionsActions,
        Preferences prefs
    ) {
        if (prefs == null) this.preferences = TempPreferences.userNodeForPackage(null);
        else this.preferences = prefs;

        this.parent = parent;

        final ResourceBundle bundle = ResourceBundle.getBundle("simplex3d/console/findreplace/Bundle");


        // Init find/replace history.
        findModel = new PrefsComboBoxModel(preferences, KEY_FIND_HISTORY);
        findModel.setSelectedItem("");

        replaceModel = new PrefsComboBoxModel(preferences, KEY_REPLACE_HISTORY);
        replaceModel.setSelectedItem("");


        // Init find/replace panels.
        final Action searchTypedAction = findReplaceActions.get(FindReplacePanel.CMD_SEARCH_TYPED);
        KeyListener searchKeyListener = new KeyListener() {
            public void keyTyped(KeyEvent e) {}
            public void keyPressed(KeyEvent e) {}
            public void keyReleased(KeyEvent e) {
                syncModel();
                searchTypedAction.actionPerformed(new ActionEvent(findPanel, 0, FindReplacePanel.CMD_SEARCH_TYPED));
            }
        };

        {
            findPanel = new FindComponent(optionsActions, findReplaceActions);
            ComboBoxEditor findEditor = findPanel.getFindComboBox().getEditor();

            findPanel.getFindComboBox().setModel(findModel);
            findPanel.getFindComboBox().addPopupMenuListener(new PrefsComboBoxListener(findModel, findEditor));

            findPanel.getFindComboBox().getEditor().getEditorComponent().addKeyListener(searchKeyListener);
        }
        {
            replacePanel = new ReplaceComponent(optionsActions, findReplaceActions);
            ComboBoxEditor findEditor = replacePanel.getFindComboBox().getEditor();
            ComboBoxEditor replaceEditor = replacePanel.getReplaceComboBox().getEditor();

            replacePanel.getFindComboBox().setModel(findModel);
            replacePanel.getFindComboBox().addPopupMenuListener(new PrefsComboBoxListener(findModel, findEditor));
            replacePanel.getFindComboBox().getEditor().getEditorComponent().addKeyListener(searchKeyListener);

            replacePanel.getReplaceComboBox().setModel(replaceModel);
            replacePanel.getReplaceComboBox().addPopupMenuListener(new PrefsComboBoxListener(replaceModel, replaceEditor));
            replacePanel.getReplaceComboBox().getEditor().getEditorComponent().addFocusListener(new FocusListener() {
                public void focusGained(FocusEvent e) {
                    replacePanel.getReplaceComboBox().getEditor().selectAll();
                }
                public void focusLost(FocusEvent e) {
                    Object component = replacePanel.getReplaceComboBox().getEditor().getEditorComponent();
                    if (component instanceof JTextField) {
                        JTextField editorField = (JTextField) component;
                        editorField.setSelectionStart(editorField.getText().length());
                    }
                }
            });
        }


        KeyStroke performActionKey = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, true);
        KeyStroke performInverseActionKey = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, InputEvent.SHIFT_DOWN_MASK, true);

        Action findNextAction = (Action) findReplaceActions.get(FindReplacePanel.CMD_FIND_NEXT);
        Action findPreviousAction = (Action) findReplaceActions.get(FindReplacePanel.CMD_FIND_PREVIOUS);
        
        {
            InputMap inputMap = findPanel.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
            inputMap.put(performActionKey, FindReplacePanel.CMD_FIND_NEXT);
            inputMap.put(performInverseActionKey, FindReplacePanel.CMD_FIND_PREVIOUS);

            ActionMap actionMap = findPanel.getActionMap();
            actionMap.put(FindReplacePanel.CMD_FIND_NEXT, findNextAction);
            actionMap.put(FindReplacePanel.CMD_FIND_PREVIOUS, findPreviousAction);
        }
        {
            InputMap inputMap = replacePanel.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
            inputMap.put(performActionKey, FindReplacePanel.CMD_FIND_NEXT);
            inputMap.put(performInverseActionKey, FindReplacePanel.CMD_FIND_PREVIOUS);

            ActionMap actionMap = replacePanel.getActionMap();
            actionMap.put(FindReplacePanel.CMD_FIND_NEXT, findNextAction);
            actionMap.put(FindReplacePanel.CMD_FIND_PREVIOUS, findPreviousAction);
        }

        matchCaseAction = (ToggleAction) optionsActions.get(FindReplacePanel.CMD_MATCH_CASE);
        wholeWordsAction = (ToggleAction) optionsActions.get(FindReplacePanel.CMD_WHOLE_WORDS);
        regularExpressionAction = (ToggleAction) optionsActions.get(FindReplacePanel.CMD_REGULAR_EXPRESSION);
        highlightAction = (ToggleAction) optionsActions.get(FindReplacePanel.CMD_HIGHLIGHT);
    }

    /** Show selected dialog with the search field set to searchTarget.
     * @param dialogType
     * @param searchTarget
     * @return false if selected dialog was already visible, true otherwise.
     */
    public boolean show(int dialogType, String searchTarget) {
        if (searchTarget != null) findModel.setSelectedItem(searchTarget);

        if (isEnabled() && getDialogType() == dialogType) {
            getActiveEditor().getEditorComponent().requestFocus();
            return false;
        }
        else {
            if (!isEnabled()) restoreLastParams(dialogType);
            else syncModel();

            setDialogType(dialogType);
            setEnabled(true);
            
            getActiveEditor().getEditorComponent().requestFocus();
            getActiveEditor().selectAll();
            return true;
        }
    }

    public SearchParams getSearchParams() {
        return new SearchParams(
            (String) findModel.getSelectedItem(), (String) replaceModel.getSelectedItem(),
            Actions.isSelected(matchCaseAction),
            Actions.isSelected(wholeWordsAction),
            Actions.isSelected(regularExpressionAction),
            (isEnabled() && Actions.isSelected(highlightAction))
        );
    }

    public void saveHistory() {
        syncModel();

        if (currentPanel == findPanel) {
            findModel.update();
            saveFindParams(getSearchParams());
        }
        else if (currentPanel == replacePanel) {
            findModel.update();
            replaceModel.update();
            saveReplaceParams(getSearchParams());
        }
    }

    public void addActionListener(ActionListener a) {
        findPanel.addActionListener(a);
        replacePanel.addActionListener(a);
    }

    public void removeActionListener(ActionListener a) {
        findPanel.removeActionListener(a);
        replacePanel.removeActionListener(a);
    }


    // Private
    private void setSearchParams(SearchParams params) {
        Actions.setSelected(matchCaseAction, params.matchCase);
        Actions.setSelected(regularExpressionAction, params.regularExpression);
        if (!params.regularExpression) {
            Actions.setSelected(wholeWordsAction, params.wholeWords);
        }
        Actions.setSelected(highlightAction, params.highlight);

        findModel.setSelectedItem(params.find);
        if (dialogType == FindReplacePanel.DIALOG_REPLACE) {
            replaceModel.setSelectedItem(params.replace);
        }
    }
    
    /** Saves the values of the textField into the comboBoxModel.
     */
    private void syncModel() {
        if (currentPanel != null) {
            Object item;

            if (currentPanel == findPanel) item = findPanel.getFindComboBox().getEditor().getItem();
            else item = replacePanel.getFindComboBox().getEditor().getItem();

            findModel.setSelectedItem(item);
        }
        replaceModel.setSelectedItem(replacePanel.getReplaceComboBox().getEditor().getItem());
    }

    private void restoreLastParams(int dialogType) {
        SearchParams lastUsed;
        if (dialogType == FindReplacePanel.DIALOG_FIND) lastUsed = loadFindParams();
        else lastUsed = loadReplaceParams();

        if (lastUsed != null) setSearchParams(lastUsed);
    }

    private SearchParams loadFindParams() {
        synchronized(lastUsedFind) {
            return lastUsedFind.get(preferences.absolutePath());
        }
    }

    private void saveFindParams(SearchParams params) {
        synchronized(lastUsedFind) {
            lastUsedFind.put(preferences.absolutePath(), params);
        }
    }

    private SearchParams loadReplaceParams() {
        synchronized(lastUsedReplace) {
            return lastUsedReplace.get(preferences.absolutePath());
        }
    }

    private void saveReplaceParams(SearchParams params) {
        synchronized(lastUsedReplace) {
            lastUsedReplace.put(preferences.absolutePath(), params);
        }
    }

    private JPanel getActivePanel() {
        switch (dialogType) {
            case FindReplacePanel.DIALOG_FIND: return findPanel;
            case FindReplacePanel.DIALOG_REPLACE: return replacePanel;
            default: return findPanel;
        }
    }

    private ComboBoxEditor getActiveEditor() {
        ComboBoxEditor editor = null;
        if (dialogType == FindReplacePanel.DIALOG_REPLACE) {
            editor = replacePanel.getFindComboBox().getEditor();
        }
        else if (dialogType == FindReplacePanel.DIALOG_FIND) {
            editor = findPanel.getFindComboBox().getEditor();
        }
        return editor;
    }


    // Getters and setters.
    public boolean isEnabled() {
        return dialogEnabled;
    }

    public int getDialogType() {
        return dialogType;
    }

    public void setEnabled(boolean dialogEnabled) {
        this.dialogEnabled = dialogEnabled;

        if (dialogEnabled && currentPanel == null) {
            JPanel dialogPanel = getActivePanel();
            parent.add(dialogPanel, java.awt.BorderLayout.PAGE_END);
            currentPanel = dialogPanel;

            parent.revalidate();
            parent.repaint();
        }
        else if (!dialogEnabled && currentPanel != null) {
            parent.remove(currentPanel);
            currentPanel = null;

            parent.revalidate();
            parent.repaint();
        }
    }

    public void setDialogType(int dialogType) {
        this.dialogType = dialogType;

        JPanel dialogPanel = getActivePanel();
        if (dialogEnabled && currentPanel != dialogPanel) {
            if (currentPanel != null) parent.remove(currentPanel);
            parent.add(dialogPanel, java.awt.BorderLayout.PAGE_END);
            currentPanel = dialogPanel;

            parent.revalidate();
            parent.repaint();
        }
    }

    public void setMaxHistoryEntries(int maxEntries) {
        findModel.setMaxHistoryEntries(maxEntries);
        replaceModel.setMaxHistoryEntries(maxEntries);
    }
}
